package com.wn.sddfp.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wn.sddfp.common.model.result.Result;
import com.wn.sddfp.common.model.result.enums.IErrorCode;
import com.wn.sddfp.common.model.result.enums.impl.BusinessCode;
import com.wn.sddfp.common.model.utils.JwtTemplate;
import com.wn.sddfp.gateway.config.IgnorePathConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;

/**
 * 网关全局过滤器，实现统一鉴权：拦截所有的请求
 */
@Configuration
public class AuthFilter implements GlobalFilter {

    @Autowired
    private IgnorePathConfig ignorePathConfig;

    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer ";

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 这里写业务逻辑 来判断 鉴权操作
     * GlobalFilter  过滤器  和   yaml中配置过滤器的区别
     *   1、GlobalFilter 全局 yaml中 是局部
     *
     * ServerWebExchange、Mono<Void> 这些API 是 reactor 技术 异步响应技术（和 springmvc中的api都是不一样）
     *
     * @param exchange the current server exchange
     * @param chain provides a way to delegate to the next filter
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //  1、获取请求路径：放行一些资源
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath();

        //路径判断类
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //拿到白名单
        List<String> whiteList = ignorePathConfig.getWhiteList();

        boolean flag = whiteList.stream().anyMatch(urlPatter -> pathMatcher.match(urlPatter,path));
        if (flag) {
            // 放行，不进行token校验
            return chain.filter(exchange);
        }
        //  2、需要先获取请求头，去掉token前缀"Bearer "， 得到token
        String authorization = request.getHeaders().getFirst(AUTHORIZATION);
        String token = "";
        if (StringUtils.isEmpty(authorization)) {
            //如果为null 进行处理
            //这里可以使用全局异常处理吗 (不能 没有springmvc 无法进行全局异常处理 )
            try {
                return responseError(response, BusinessCode.USER_TOKEN_ERROR);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        token = authorization.replace("Bearer ","");
        System.out.println("token = " + token);
        //  3、校验token
        boolean isSuccess = jwtTemplate.verify(token);
        if (!isSuccess){
            try {
                return responseError(response, BusinessCode.USER_TOKEN_ERROR2);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        //  4、解析获取token数据： userId,
        Object object = jwtTemplate.parseToken(token, "userId");
        Long userId =  Long.valueOf(object.toString());
        System.out.println("userId = " + userId);
        // 校验token是否过期(直接查询redis中是否存在)
        String keyToken = "system:token:" + userId;
        //获取redis中的token
        String redisToken = stringRedisTemplate.opsForValue().get(keyToken);
        if (StringUtils.isEmpty(redisToken)) {
            //如果为null
            try { //token超时
                return responseError(response, BusinessCode.TOKEN_IS_EXPIRED);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        //  5、token 续期  30min
        stringRedisTemplate.expire(keyToken, Duration.ofMinutes(30));
        //  6、网关中重写请求头，让请求头携带userId，再转发到微服务，这样在微服务中就可以直接获取登录用户id
        //  7、微服务直接从请求头获取用户id
        request.mutate().header("userId",userId.toString());

        //进行放行操作
        return chain.filter(exchange);
    }


    private static Mono<Void> responseError(ServerHttpResponse response, IErrorCode resultCode) throws Exception {
        // 返回错误
        Result<Object> result = Result.fail(resultCode);
        //把 结果转成  byte 数组
        byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
        //这里使用的事 reactor 技术 了解就可以
        //数据缓存
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        //返回结果
        return response.writeWith(Mono.just(dataBuffer));
    }

}